﻿namespace BaseTools.UI.ControlExtensions
{

#if WINRT
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Media.Animation;
    using Windows.UI.Xaml.Input;
    using Windows.Foundation;
    using System.Reflection;
#endif

#if SILVERLIGHT
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Input;
    using Microsoft.Phone.Controls;
#endif

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Text;
    using System.Windows;
    using BaseTools.UI.Input;
    using BaseTools.UI.Common;
    
    /// <summary>
    /// This code provides attached properties for adding a 'tilt' effect to all 
    /// controls within a container.
    /// </summary>
    /// <QualityBand>Preview</QualityBand>
    [SuppressMessage("Microsoft.Design", "CA1052:StaticHolderTypesShouldBeSealed", Justification = "Cannot be static and derive from DependencyObject.")]
    public partial class TiltEffect : DependencyObject
    {
        /// <summary>
        ///  Limit of control height or width, on which usual tilt effect applied.
        ///  If size of control more, efect modified as he pressed to center of control.  
        /// </summary>
        private const double NotCenteredEffectControlSizeLimit = 500;

        /// <summary>
        /// Cache of previous cache modes. Not using weak references for now.
        /// </summary>
        private static Dictionary<DependencyObject, CacheMode> _originalCacheMode = new Dictionary<DependencyObject, CacheMode>();

        /// <summary>
        /// Maximum amount of tilt, in radians.
        /// </summary>
        private const double MaxAngle = 0.3;

        /// <summary>
        /// Maximum amount of depression, in pixels
        /// </summary>
        private const double MaxDepression = 25;

        /// <summary>
        /// Delay between releasing an element and the tilt release animation 
        /// playing.
        /// </summary>
        private static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200);

        /// <summary>
        /// Duration of tilt release animation.
        /// </summary>
        private static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100);

        /// <summary>
        /// The control that is currently being tilted.
        /// </summary>
        private static FrameworkElement currentTiltElement;

        /// <summary>
        /// The single instance of a storyboard used for all tilts.
        /// </summary>
        private static Storyboard tiltReturnStoryboard;

        /// <summary>
        /// The single instance of an X rotation used for all tilts.
        /// </summary>
        private static DoubleAnimation tiltReturnXAnimation;

        /// <summary>
        /// The single instance of a Y rotation used for all tilts.
        /// </summary>
        private static DoubleAnimation tiltReturnYAnimation;

        /// <summary>
        /// The single instance of a Z depression used for all tilts.
        /// </summary>
        private static DoubleAnimation tiltReturnZAnimation;

        /// <summary>
        /// The center of the tilt element.
        /// </summary>
        private static Point currentTiltElementCenter;

        /// <summary>
        /// Whether the animation just completed was for a 'pause' or not.
        /// </summary>
        private static bool wasPauseAnimation = false;

        private static ManipulationListener manipulationListener = new ManipulationListener();

        /// <summary>
        /// Whether to use a slightly more accurate (but slightly slower) tilt 
        /// animation easing function.
        /// </summary>
        public static bool UseLogarithmicEase { get; set; }

        /// <summary>
        /// Default list of items that are tiltable.
        /// </summary>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tiltable", Justification = "By design.")]
        [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Keeping it simple.")]
        public static List<Type> TiltableItems { get; private set; }

        #region Constructor and Static Constructor
        /// <summary>
        /// This is not a constructable class, but it cannot be static because 
        /// it derives from DependencyObject.
        /// </summary>
        private TiltEffect()
        {
        }

        /// <summary>
        /// Initialize the static properties
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to initialize the tiltable items property.")]
        static TiltEffect()
        {
                // The tiltable items list.
                TiltableItems = new List<Type>();
                manipulationListener.ManipulationStarted += TiltEffect_ManipulationStarted;
                manipulationListener.ManipulationDelta += TiltEffect_ManipulationDelta;
                manipulationListener.ManipulationCompleted += TiltEffect_ManipulationCompleted;
#if WINRT
                if (!EnvironmentInfo.Current.IsInDesignMode)
                {
                    Window.Current.CoreWindow.VisibilityChanged += CoreWindow_VisibilityChanged;
                }
#endif
        }

#if WINRT
        static void CoreWindow_VisibilityChanged(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.VisibilityChangedEventArgs args)
        {
            if (!args.Visible)
            {
                if (currentTiltElement != null)
                {
                    EndTiltEffect(currentTiltElement);
                }
            }
        }
#endif
        
        #endregion

        #region Dependency properties

        /// <summary>
        /// Whether the tilt effect is enabled on a container (and all its 
        /// children).
        /// </summary>
        public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached(
          "IsTiltEnabled",
          typeof(bool),
          typeof(TiltEffect),
          new PropertyMetadata(false, OnIsTiltEnabledChanged)
          );

        /// <summary>
        /// Gets the IsTiltEnabled dependency property from an object.
        /// </summary>
        /// <param name="source">The object to get the property from.</param>
        /// <returns>The property's value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static bool GetIsTiltEnabled(DependencyObject source)
        {
            return (bool)source.GetValue(IsTiltEnabledProperty);
        }

        /// <summary>
        /// Sets the IsTiltEnabled dependency property on an object.
        /// </summary>
        /// <param name="source">The object to set the property on.</param>
        /// <param name="value">The value to set.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static void SetIsTiltEnabled(DependencyObject source, bool value)
        {
            source.SetValue(IsTiltEnabledProperty, value);
        }

        /// <summary>
        /// Suppresses the tilt effect on a single control that would otherwise 
        /// be tilted. If control is inside another tiltable control, tilt effect showed for parent.
        /// </summary>
        public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached(
          "SuppressTilt",
          typeof(bool),
          typeof(TiltEffect),
          null
          );

        /// <summary>
        /// Disable the tilt effect on a single control that would otherwise 
        /// be tilted. If control is inside another control with same effect, tilt effect not showed anyway.
        /// </summary>
        public static readonly DependencyProperty DisableTiltProperty = DependencyProperty.RegisterAttached(
          "DisableTilt",
          typeof(bool),
          typeof(TiltEffect),
          null
          );


        /// <summary>
        /// Gets a value indicates whether tilt effect will be applied with the center mode. (Uniform pressing).
        /// </summary>
        /// <param name="obj">Associated control.</param>
        /// <returns>True, is center mode is used. Otherwise false</returns>
        public static bool GetIsCenterModeUsed(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsCenterModeUsedProperty);
        }
        
        /// <summary>
        /// Sets a value indicates whether tilt effect will be applied with the center mode. (Uniform pressing).
        /// </summary>
        /// <param name="obj">Associated control.</param>
        /// <param name="value">New value of property.</param>
        public static void SetIsCenterModeUsed(DependencyObject obj, bool value)
        {
            obj.SetValue(IsCenterModeUsedProperty, value);
        }

        /// <summary>
        /// Identifies the <see cref="TiltEffect"/>.IsCenterModeUsed attached property.
        /// </summary>
        public static readonly DependencyProperty IsCenterModeUsedProperty =
            DependencyProperty.RegisterAttached("IsCenterModeUsed", typeof(bool), typeof(TiltEffect), new PropertyMetadata(false));

        /// <summary>
        /// Get value of DisableTiltProperty associated with control. 
        /// </summary>
        /// <param name="source">Associated control.</param>
        /// <returns>Value of DisableTiltProperty. If this value is true and control pressed, tilt effect not used.</returns>
        public static bool GetDisableTilt(DependencyObject source)
        {
            return (bool)source.GetValue(DisableTiltProperty);
        }

        /// <summary>
        /// Set value of DisableTiltProperty associated with control. 
        /// </summary>
        /// <param name="source">Associated control.</param>
        /// <param name="value">New value of DisableTiltProperty. If this value is true and control pressed, tilt effect not used.</param>
        public static void SetDisableTilt(DependencyObject source, bool value)
        {
            source.SetValue(DisableTiltProperty, value);
        }

        /// <summary>
        /// Gets the SuppressTilt dependency property from an object.
        /// </summary>
        /// <param name="source">The object to get the property from.</param>
        /// <returns>The property's value.</returns>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static bool GetSuppressTilt(DependencyObject source)
        {
            return (bool)source.GetValue(SuppressTiltProperty);
        }

        /// <summary>
        /// Sets the SuppressTilt dependency property from an object.
        /// </summary>
        /// <param name="source">The object to get the property from.</param>
        /// <param name="value">The property's value.</param>
        [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")]
        public static void SetSuppressTilt(DependencyObject source, bool value)
        {
            source.SetValue(SuppressTiltProperty, value);
        }

        /// <summary>
        /// Property change handler for the IsTiltEnabled dependency property.
        /// </summary>
        /// <param name="target">The element that the property is atteched to.</param>
        /// <param name="args">Event arguments.</param>
        /// <remarks>
        /// Adds or removes event handlers from the element that has been 
        /// (un)registered for tilting.
        /// </remarks>
        static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
        {
            FrameworkElement fe = target as FrameworkElement;
            if (fe != null)
            {
                // Add / remove the event handler if necessary
                if ((bool)args.NewValue == true)
                {
#if WINRT
                    //fe.ManipulationMode = ManipulationModes.TranslateX | ManipulationModes.TranslateY;
#endif

                    manipulationListener.AttachToManipulationStarted(fe);
                }
                else
                {
                    manipulationListener.DetachFromManipulationStarted(fe);
                }
            }
        }

        #endregion

        #region Top-level manipulation event handlers

        /// <summary>
        /// Event handler for ManipulationStarted.
        /// </summary>
        /// <param name="sender">sender of the event - this will be the tilt 
        /// container (eg, entire page).</param>
        /// <param name="e">Event arguments.</param>
        private static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedSharedEventArgs e)
        {
            TryStartTiltEffect(sender as FrameworkElement, e);
        }

        /// <summary>
        /// Event handler for ManipulationDelta
        /// </summary>
        /// <param name="sender">sender of the event - this will be the tilting 
        /// object (eg a button).</param>
        /// <param name="e">Event arguments.</param>
        private static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaSharedEventArgs e)
        {
            ContinueTiltEffect(sender as FrameworkElement, e);
        }

        /// <summary>
        /// Event handler for ManipulationCompleted.
        /// </summary>
        /// <param name="sender">sender of the event - this will be the tilting 
        /// object (eg a button).</param>
        /// <param name="e">Event arguments.</param>
        private static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedSharedEventArgs e)

        {
            EndTiltEffect(currentTiltElement);
        }

        #endregion

        #region Core tilt logic

        /// <summary>
        /// Checks if the manipulation should cause a tilt, and if so starts the 
        /// tilt effect.
        /// </summary>
        /// <param name="source">The source of the manipulation (the tilt 
        /// container, eg entire page).</param>
        /// <param name="e">The args from the ManipulationStarted event.</param>
        static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedSharedEventArgs e)
        {
            // CHANGES:
            // Make tilt effect enabled to an control to which IsTiltEnabledProperty setted
            // Fix effect for very big controls.
            // Add property that allow disable tilt.
            var pressedFrameworkElemnt = (FrameworkElement)e.OriginalSource;
            var visualAncectors = IEnumerableConverter.ConvertToEnumerable<FrameworkElement>(pressedFrameworkElemnt);
            visualAncectors = visualAncectors.Concat(pressedFrameworkElemnt.GetVisualAncestors());
            foreach (FrameworkElement ancestor in visualAncectors)
            {
                var isDisableTilt = GetDisableTilt(ancestor);
                if (!isDisableTilt)
                {
                    bool canApplyEffect = GetIsTiltEnabled(ancestor);
                    if (!canApplyEffect)
                    {
                        foreach (Type t in TiltableItems)
                        {
#if SILVERLIGHT
                            canApplyEffect = t.IsAssignableFrom(ancestor.GetType());
#endif

#if WINRT
                            canApplyEffect = t.GetTypeInfo().IsAssignableFrom(ancestor.GetType().GetTypeInfo());
#endif
                            if (canApplyEffect)
                            {
                                break;
                            }
                        }
                    }

                    if (canApplyEffect)
                    {
                        canApplyEffect = !GetSuppressTilt(ancestor);
                    }
                    
                    if (canApplyEffect)
                    {
                        FrameworkElement element = ancestor;
                        FrameworkElement container = e.ManipulationContainer as FrameworkElement;
                        if (element == null || container == null)
                            return;

                        // Touch point relative to the element being tilted.
                        Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);

                        Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
                        var isCenterMode = IsEffectWithCenterMode(element);
                        if (isCenterMode)
                        {
                            tiltTouchPoint = elementCenter;
                        }
                        // Center of the element being tilted.
                        

                        // Camera adjustment.
                        Point centerToCenterDelta = GetCenterToCenterDelta(element, element);

                        BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta);
                        return;
                    }
                }
                else
                {
                    break;
                }
            }
            // CHANGES_END
        }

        private static bool IsEffectWithCenterMode(FrameworkElement element)
        {
            var isCenterMode = GetIsCenterModeUsed(element);
            if (!isCenterMode)
            {

                if (element.ActualHeight > NotCenteredEffectControlSizeLimit || element.ActualWidth > NotCenteredEffectControlSizeLimit)
                {

                    isCenterMode = true;

                }
            }

            return isCenterMode;
        }

        /// <summary>
        /// Computes the delta between the centre of an element and its 
        /// container.
        /// </summary>
        /// <param name="element">The element to compare.</param>
        /// <param name="container">The element to compare against.</param>
        /// <returns>A point that represents the delta between the two centers.</returns>
        static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container)
        {
            Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2);
            Point containerCenter;

#if WINDOWS_PHONE

            // Need to special-case the frame to handle different orientations.
            PhoneApplicationFrame frame = container as PhoneApplicationFrame;
            if (frame != null)
            {
                // Switch width and height in landscape mode
                if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape)
                {
                    containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2);
                }
                else
                {
                    containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
                }
            }
            else
            {
                containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
            }
#else
            containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2);
#endif

            Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter);
            Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y);

            return result;
        }

        /// <summary>
        /// Begins the tilt effect by preparing the control and doing the 
        /// initial animation.
        /// </summary>
        /// <param name="element">The element to tilt.</param>
        /// <param name="touchPoint">The touch point, in element coordinates.</param>
        /// <param name="centerPoint">The center point of the element in element
        /// coordinates.</param>
        /// <param name="centerDelta">The delta between the 
        /// <paramref name="element"/>'s center and the container's center.</param>
        static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta)
        {
            if (tiltReturnStoryboard != null)
            {
                StopTiltReturnStoryboardAndCleanup();
            }

            if (PrepareControlForTilt(element, centerDelta) == false)
            {
                return;
            }

            currentTiltElement = element;
            currentTiltElementCenter = centerPoint;
            PrepareTiltReturnStoryboard(element);

            ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter);
        }

        /// <summary>
        /// Prepares a control to be tilted by setting up a plane projection and
        /// some event handlers.
        /// </summary>
        /// <param name="element">The control that is to be tilted.</param>
        /// <param name="centerDelta">Delta between the element's center and the
        /// tilt container's.</param>
        /// <returns>true if successful; false otherwise.</returns>
        /// <remarks>
        /// This method is conservative; it will fail any attempt to tilt a 
        /// control that already has a projection on it.
        /// </remarks>
        static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta)
        {
            // Prevents interference with any existing transforms
            if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform)))
            {
                return false;
            }

            _originalCacheMode[element] = element.CacheMode;
            // change - fix displaying control with sizes more 2048px
            //element.CacheMode = new BitmapCache();

            TranslateTransform transform = new TranslateTransform();
            transform.X = centerDelta.X;
            transform.Y = centerDelta.Y;
            element.RenderTransform = transform;

            PlaneProjection projection = new PlaneProjection();
            projection.GlobalOffsetX = -1 * centerDelta.X;
            projection.GlobalOffsetY = -1 * centerDelta.Y;
            element.Projection = projection;

            manipulationListener.AttachToManipulationDelta(element);
            manipulationListener.AttachToManipulationCompleted(element);
            return true;
        }

        /// <summary>
        /// Removes modifications made by PrepareControlForTilt.
        /// </summary>
        /// <param name="element">THe control to be un-prepared.</param>
        /// <remarks>
        /// This method is basic; it does not do anything to detect if the 
        /// control being un-prepared was previously prepared.
        /// </remarks>
        private static void RevertPrepareControlForTilt(FrameworkElement element)
        {
            manipulationListener.DetachFromManipulationDelta(element);
            manipulationListener.DetachFromManipulationCompleted(element);
            element.Projection = null;
            element.RenderTransform = null;

            // change - fix displaying control with sizes more 2048px
            //CacheMode original;
            //if (_originalCacheMode.TryGetValue(element, out original))
            //{
            //    element.CacheMode = original;
            //    _originalCacheMode.Remove(element);
            //}
            //else
            //{
            //    element.CacheMode = null;
            //}
        }

        /// <summary>
        /// Creates the tilt return storyboard (if not already created) and 
        /// targets it to the projection.
        /// </summary>
        /// <param name="element">The framework element to prepare for
        /// projection.</param>
        static void PrepareTiltReturnStoryboard(FrameworkElement element)
        {
            if (tiltReturnStoryboard == null)
            {
                tiltReturnStoryboard = new Storyboard();
                tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed;

                tiltReturnXAnimation = new DoubleAnimation();
#if SILVERLIGHT
                Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty));
#endif
#if WINRT
                Storyboard.SetTargetProperty(tiltReturnXAnimation, "RotationX");
#endif
                tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay;
                tiltReturnXAnimation.To = 0;
                tiltReturnXAnimation.Duration = TiltReturnAnimationDuration;

                tiltReturnYAnimation = new DoubleAnimation();
#if SILVERLIGHT
                Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty));
#endif
#if WINRT
                Storyboard.SetTargetProperty(tiltReturnYAnimation, "RotationY");
#endif
                tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay;
                tiltReturnYAnimation.To = 0;
                tiltReturnYAnimation.Duration = TiltReturnAnimationDuration;

                tiltReturnZAnimation = new DoubleAnimation();
#if SILVERLIGHT

                Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty));
#endif
#if WINRT
                Storyboard.SetTargetProperty(tiltReturnZAnimation, "GlobalOffsetZ");
#endif
                tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay;
                tiltReturnZAnimation.To = 0;
                tiltReturnZAnimation.Duration = TiltReturnAnimationDuration;

#if SILVERLIGHT
                if (UseLogarithmicEase)
                {
                    tiltReturnXAnimation.EasingFunction = new LogarithmicEase();
                    tiltReturnYAnimation.EasingFunction = new LogarithmicEase();
                    tiltReturnZAnimation.EasingFunction = new LogarithmicEase();
                }
#endif

                tiltReturnStoryboard.Children.Add(tiltReturnXAnimation);
                tiltReturnStoryboard.Children.Add(tiltReturnYAnimation);
                tiltReturnStoryboard.Children.Add(tiltReturnZAnimation);
            }

            Storyboard.SetTarget(tiltReturnXAnimation, element.Projection);
            Storyboard.SetTarget(tiltReturnYAnimation, element.Projection);
            Storyboard.SetTarget(tiltReturnZAnimation, element.Projection);
        }

        /// <summary>
        /// Continues a tilt effect that is currently applied to an element, 
        /// presumably because the user moved their finger.
        /// </summary>
        /// <param name="element">The element being tilted.</param>
        /// <param name="e">The manipulation event args.</param>
        static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaSharedEventArgs e)
        {
            FrameworkElement container = e.ManipulationContainer as FrameworkElement;
            if (container == null || element == null)
            {
                return;
            }

            Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin);

            // If touch moved outside bounds of element, then pause the tilt 
            // (but don't cancel it)
            if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true)
            {
                PauseTiltEffect();
            }
            else
            {
                // CHANGES:
                tiltTouchPoint = e.ManipulationOrigin;
                var isCenterMode = IsEffectWithCenterMode(currentTiltElement);
                if (!isCenterMode)
                {
                    // Apply the updated tilt effect
                    ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter);
                }
            }
        }

        /// <summary>
        /// Ends the tilt effect by playing the animation.
        /// </summary>
        /// <param name="element">The element being tilted.</param>
        private static void EndTiltEffect(FrameworkElement element)
        {
            if (element != null)
            {
                manipulationListener.DetachFromManipulationDelta(element);
                manipulationListener.DetachFromManipulationCompleted(element);
            }

            if (tiltReturnStoryboard != null)
            {
                wasPauseAnimation = false;
                if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active)
                {
                    tiltReturnStoryboard.Begin();
                }
            }
            else
            {
                StopTiltReturnStoryboardAndCleanup();
            }
        }

        /// <summary>
        /// Handler for the storyboard complete event.
        /// </summary>
        /// <param name="sender">sender of the event.</param>
        /// <param name="e">event args.</param>
#if SILVERLIGHT
        private static void TiltReturnStoryboard_Completed(object sender, EventArgs e)
#endif
#if WINRT
        private static void TiltReturnStoryboard_Completed(object sender, object e)
#endif
        {
            if (wasPauseAnimation)
            {
                ResetTiltEffect(currentTiltElement);
            }
            else
            {
                StopTiltReturnStoryboardAndCleanup();
            }
        }

        /// <summary>
        /// Resets the tilt effect on the control, making it appear 'normal'
        /// again.
        /// </summary>
        /// <param name="element">The element to reset the tilt on.</param>
        /// <remarks>
        /// This method doesn't turn off the tilt effect or cancel any current
        /// manipulation; it just temporarily cancels the effect.
        /// </remarks>
        private static void ResetTiltEffect(FrameworkElement element)
        {
            PlaneProjection projection = element.Projection as PlaneProjection;
            projection.RotationY = 0;
            projection.RotationX = 0;
            projection.GlobalOffsetZ = 0;
        }

        /// <summary>
        /// Stops the tilt effect and release resources applied to the currently
        /// tilted control.
        /// </summary>
        private static void StopTiltReturnStoryboardAndCleanup()
        {
            if (tiltReturnStoryboard != null)
            {
                tiltReturnStoryboard.Stop();
            }

            if (currentTiltElement != null)
            {
                RevertPrepareControlForTilt(currentTiltElement);
                currentTiltElement = null;
            }
        }

        /// <summary>
        /// Pauses the tilt effect so that the control returns to the 'at rest'
        /// position, but doesn't stop the tilt effect (handlers are still 
        /// attached).
        /// </summary>
        private static void PauseTiltEffect()
        {
            if ((tiltReturnStoryboard != null) && !wasPauseAnimation)
            {
                tiltReturnStoryboard.Stop();
                wasPauseAnimation = true;
                tiltReturnStoryboard.Begin();
            }
        }

        /// <summary>
        /// Resets the storyboard to not running.
        /// </summary>
        private static void ResetTiltReturnStoryboard()
        {
            tiltReturnStoryboard.Stop();
            wasPauseAnimation = false;
        }

        /// <summary>
        /// Applies the tilt effect to the control.
        /// </summary>
        /// <param name="element">the control to tilt.</param>
        /// <param name="touchPoint">The touch point, in the container's 
        /// coordinates.</param>
        /// <param name="centerPoint">The center point of the container.</param>
        private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint)
        {
            // Stop any active animation
            ResetTiltReturnStoryboard();

            // Get relative point of the touch in percentage of container size
            Point normalizedPoint = new Point(
                Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1),
                Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1));

            if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y))
            {
                return;
            }

            // Shell values
            double xMagnitude = Math.Abs(normalizedPoint.X - 0.5);
            double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5);
            double xDirection = -Math.Sign(normalizedPoint.X - 0.5);
            double yDirection = Math.Sign(normalizedPoint.Y - 0.5);
            double angleMagnitude = xMagnitude + yMagnitude;
            double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0;

            double angle = angleMagnitude * MaxAngle * 180 / Math.PI;
            double depression = (1 - angleMagnitude) * MaxDepression;


            // Changes: Here we configure value of tilt depression depending on sizes of control.
            // Press on small item almost not visible, press on large item too big.  
            if (element.ActualHeight > 0 && element.ActualWidth > 0)
            {
                double largerSize = Math.Max(element.ActualHeight, element.ActualWidth);
                depression /= Math.Pow((largerSize / 400), 0.75);
            }

            // RotationX and RotationY are the angles of rotations about the x- 
            // or y-*axis*; to achieve a rotation in the x- or y-*direction*, we 
            // need to swap the two. That is, a rotation to the left about the 
            // y-axis is a rotation to the left in the x-direction, and a 
            // rotation up about the x-axis is a rotation up in the y-direction.
            PlaneProjection projection = element.Projection as PlaneProjection;
            projection.RotationY = angle * xAngleContribution * xDirection;
            projection.RotationX = angle * (1 - xAngleContribution) * yDirection;
            projection.GlobalOffsetZ = -depression;
        }

        #endregion

        #region Custom easing function

#if SILVERLIGHT
        /// <summary>
        /// Provides an easing function for the tilt return.
        /// </summary>
        private class LogarithmicEase : EasingFunctionBase
        {
            /// <summary>
            /// Computes the easing function.
            /// </summary>
            /// <param name="normalizedTime">The time.</param>
            /// <returns>The eased value.</returns>
            protected override double EaseInCore(double normalizedTime)
            {
                // ln(t + 1) / ln(2)
                return Math.Log(normalizedTime + 1) / 0.693147181;
            }
        }
#endif

        #endregion
    }

    /// <summary>
    /// Linq extentions. change.
    /// </summary>
    static class IEnumerableConverter
    {
        /// <summary>
        /// Convert single object to enumerable that contains only this object
        /// </summary>
        /// <typeparam name="T">Object type</typeparam>
        /// <param name="item">object for convertion to IEnumerable</param>
        /// <returns>object for convertion to IEnumerable</returns>
        public static IEnumerable<T> ConvertToEnumerable<T>(T item)
        {
            yield return item;
        }
    }

    ///// <summary>
    ///// Couple of simple helpers for walking the visual tree.
    ///// </summary>
    //static class TreeHelpers
    //{
    //    /// <summary>
    //    /// Gets the ancestors of the element, up to the root.
    //    /// </summary>
    //    /// <param name="node">The element to start from.</param>
    //    /// <returns>An enumerator of the ancestors.</returns>
    //    public static IEnumerable<FrameworkElement> GetVisualAncestors(this FrameworkElement node)
    //    {
    //        FrameworkElement parent = node.GetVisualParent();
    //        while (parent != null)
    //        {
    //            yield return parent;
    //            parent = parent.GetVisualParent();
    //        }
    //    }

    //    /// <summary>
    //    /// Gets the visual parent of the element.
    //    /// </summary>
    //    /// <param name="node">The element to check.</param>
    //    /// <returns>The visual parent.</returns>
    //    public static FrameworkElement GetVisualParent(this FrameworkElement node)
    //    {
    //        return VisualTreeHelper.GetParent(node) as FrameworkElement;
    //    }
    //}
#if WINRT

    internal static class TransformExtensions
    {
        public static Point Transform(this GeneralTransform source, Point point)
        {
            return source.TransformPoint(point);
        }
    }
#endif
}
